package com.springboot.file.listener;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.springboot.file.entity.InvoiceDetailRecord;
import com.springboot.file.exception.BusinessException;
import com.springboot.file.repository.InvoiceDetailRecordRepository;
import com.springboot.file.util.DateUtil;
import com.springboot.file.util.easyexcel.annotation.EasyImport;
import com.springboot.file.util.easyexcel.constant.BusinessType;
import com.springboot.file.util.easyexcel.listener.AbstractBusinessListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 发票清单信息导入监听器
 */
@Slf4j
@Component
@EasyImport(value = BusinessType.INVOICE, description = "发票清单信息导入")
public class InvoiceDetailRecordListener extends AbstractBusinessListener<InvoiceDetailRecord> {

    /**
     * 每批次处理数量，避免一次性处理过多数据
     */
    private static final int SUB_BATCH_SIZE = 100;

    /**
     * 记录已保存的记录总数
     */
    private final AtomicInteger savedCount = new AtomicInteger(0);

    /**
     * 记录更新的记录总数
     */
    private final AtomicInteger updatedCount = new AtomicInteger(0);

    @Resource
    private InvoiceDetailRecordRepository invoiceDetailRecordRepository;

    @Override
    protected int saveData(List<InvoiceDetailRecord> dataList) {
        if (ObjectUtil.isEmpty(dataList)) {
            log.info("没有数据需要保存");
            return 0;
        }

        log.info("开始处理发票数据，记录数: {}", dataList.size());

        try {
            // 分批次处理数据，减小单次处理量
            List<List<InvoiceDetailRecord>> subLists = CollUtil.split(dataList, SUB_BATCH_SIZE);
            log.info("发票数据分为{}个批次处理", subLists.size());

            for (int i = 0; i < subLists.size(); i++) {
                processBatch(subLists, i);

                // 帮助 GC 清理内存
                subLists.set(i, null);
            }

            log.info("发票数据导入完成，成功保存 {} 条记录，更新 {} 条记录状态",
                    savedCount.get(), updatedCount.get());
                    
            return savedCount.get();
        } catch (Exception e) {
            log.error("处理发票数据时发生异常", e);
            return 0;
        }
    }

    /**
     * 处理一个批次的数据
     *
     * @param subLists 所有分批数据
     * @param batchIndex 当前批次索引
     */
    private void processBatch(List<List<InvoiceDetailRecord>> subLists, int batchIndex) {
        List<InvoiceDetailRecord> subList = subLists.get(batchIndex);
        List<InvoiceDetailRecord> finishList = new ArrayList<>(subList.size());

        // 处理已存在的记录，并返回需要新增的记录
        List<InvoiceDetailRecord> recordsToAdd = handleExistingRecords(subList);

        // 准备新记录
        prepareNewRecords(recordsToAdd, finishList);

        // 保存新记录
        saveBatchRecords(finishList, batchIndex + 1, subLists.size());

        // 帮助 GC 清理内存
        finishList.clear();
    }

    /**
     * 处理已存在的记录
     *
     * @param dataList 当前批次数据
     * @return 需要新增的记录列表
     */
    private List<InvoiceDetailRecord> handleExistingRecords(List<InvoiceDetailRecord> dataList) {
        // 提取所有应付账款凭证编号
        Set<String> voucherNos = extractVoucherNumbers(dataList);

        if (voucherNos.isEmpty()) {
            return dataList;
        }

        // 查询已存在的记录
        List<InvoiceDetailRecord> existingRecords = findExistingRecords(voucherNos);

        if (existingRecords.isEmpty()) {
            return dataList;
        }

        // 将已存在的记录按凭证编号分组
        Map<String, InvoiceDetailRecord> existingMap = existingRecords.stream()
                .collect(Collectors.toMap(
                    InvoiceDetailRecord::getNeedPayAccEvidenceCode,
                    invoiceRecord -> invoiceRecord,
                    (existing, replacement) -> existing  // 如果遇到重复键，保留已存在的记录
                ));

        // 处理每条导入数据，并收集需要新增的记录
        List<InvoiceDetailRecord> recordsToAdd = new ArrayList<>();
        for (InvoiceDetailRecord importedRecord : dataList) {
            String voucherNo = importedRecord.getNeedPayAccEvidenceCode();
            InvoiceDetailRecord existingRecord = existingMap.get(voucherNo);

            if (existingRecord != null) {
                // 更新已存在记录
                updateExistingRecord(existingRecord, importedRecord);
                updatedCount.incrementAndGet();
            } else {
                // 收集需要新增的记录
                recordsToAdd.add(importedRecord);
            }
        }

        return recordsToAdd;
    }

    /**
     * 更新已存在的记录
     *
     * @param existingRecord 已存在的记录
     * @param importedRecord 导入的记录
     */
    private void updateExistingRecord(InvoiceDetailRecord existingRecord, InvoiceDetailRecord importedRecord) {
        // 复制导入记录的所有属性到已存在记录
        BeanUtil.copyProperties(importedRecord, existingRecord, "id", "createBy", "createTime");
        try {
            // 设置更新信息
            existingRecord.setUpdateTime(LocalDateTime.now());
            // 更新记录
            invoiceDetailRecordRepository.updateById(existingRecord);
        } catch (Exception e) {
            log.error("更新记录时发生异常", e);
            throw new BusinessException("更新记录失败：" +e.getMessage());
        }
    }

    /**
     * 从数据列表中提取所有应付账款凭证编号
     *
     * @param dataList 数据列表
     * @return 凭证编号集合
     */
    private Set<String> extractVoucherNumbers(List<InvoiceDetailRecord> dataList) {
        return dataList.stream()
                .map(InvoiceDetailRecord::getNeedPayAccEvidenceCode)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());
    }

    /**
     * 查询已存在的记录
     *
     * @param voucherNos 凭证编号集合
     * @return 已存在的记录列表
     */
    private List<InvoiceDetailRecord> findExistingRecords(Set<String> voucherNos) {
        LambdaQueryWrapper<InvoiceDetailRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(InvoiceDetailRecord::getNeedPayAccEvidenceCode, voucherNos);
        return invoiceDetailRecordRepository.list(queryWrapper);
    }

    /**
     * 准备要保存的新记录
     *
     * @param sourceList 源数据列表
     * @param targetList 目标列表，用于存放准备好的新记录
     */
    private void prepareNewRecords(List<InvoiceDetailRecord> sourceList, List<InvoiceDetailRecord> targetList) {
        for (InvoiceDetailRecord source : sourceList) {
            InvoiceDetailRecord target = new InvoiceDetailRecord();
            BeanUtil.copyProperties(source, target);
            // 转换日期格式
            target.setId(IdUtil.simpleUUID());
            target.setCreateTime(LocalDateTime.now());
            targetList.add(target);
        }
    }

    /**
     * 批量保存记录
     *
     * @param records 要保存的记录列表
     * @param batchNumber 当前批次号
     * @param totalBatches 总批次数
     */
    private void saveBatchRecords(List<InvoiceDetailRecord> records, int batchNumber, int totalBatches) {
        if (records.isEmpty()) {
            log.info("批次 {}/{} 没有记录需要保存", batchNumber, totalBatches);
            return;
        }

        try {
            // 批量保存数据
            boolean saveResult = invoiceDetailRecordRepository.saveBatch(records);
            if (saveResult) {
                // 更新已保存记录计数
                savedCount.addAndGet(records.size());
                log.info("批次 {}/{} 处理完成，共保存 {} 条记录",
                        batchNumber, totalBatches, records.size());
            } else {
                log.error("批次 {}/{} 批量保存失败", batchNumber, totalBatches);
            }
        } catch (Exception e) {
            log.error("保存发票数据批次 {}/{} 失败，该批次包含 {} 条记录",
                    batchNumber, totalBatches, records.size(), e);

            // 打印前几条失败的数据供调试
            logErrorSamples(records);
        }
    }

    /**
     * 记录错误样本
     *
     * @param records 记录列表
     */
    private void logErrorSamples(List<InvoiceDetailRecord> records) {
        int errorSampleSize = Math.min(3, records.size());
        for (int j = 0; j < errorSampleSize; j++) {
            log.error("错误样本数据 {}: {}", j+1, records.get(j));
        }
    }
}
